﻿/******************************************************************************
* SunnyUI.FrameDecoder 开源TCP、串口数据解码库。
* CopyRight (C) 2022-2023 ShenYongHua(沈永华).
* QQ群：56829229 QQ：17612584 EMail：SunnyUI@qq.com
*
* Blog:   https://www.cnblogs.com/yhuse
* Gitee:  https://gitee.com/yhuse/SunnyUI.FrameDecoder
*
* SunnyUI.FrameDecoder.dll can be used for free under the MIT license.
* If you use this code, please keep this note.
* 如果您使用此代码，请保留此说明。
******************************************************************************
* 文件名称: DecoderHelper.cs
* 文件说明: 数据帧解码器帮助类
* 当前版本: V1.0
* 创建日期: 2022-11-01
*
* 2022-11-01: V1.0.0 增加文件说明
******************************************************************************/

using System;
using System.Runtime.InteropServices;
using System.Text;

namespace Sunny.FrameDecoder;

/// <summary>
/// 字节数组解码器复制副本接口
/// </summary>
public interface IByteFrameDecoderCloneable
{
    /// <summary>
    /// 复制一个字节数组解码器
    /// </summary>
    /// <returns>字节数组解码器</returns>
    BaseByteFrameDecoder Clone();
}

internal static class ConstDefine
{
    public const int DefaultNetBufferSize = 1024 * 1024;

    public const int CacheSize = 1024;
}

/// <summary>
/// 数据帧解码器帮助类
/// </summary>
public static class DecoderHelper
{
    /// <summary>
    /// 返回基于内存可复用的字节数组对象
    /// </summary>
    /// <typeparam name="T">类型</typeparam>
    /// <param name="value">结构对象</param>
    /// <returns>字节数组对象</returns>
    public unsafe static ByteArray ToByteArray<T>(this T value) where T : struct
    {
        //得到结构体的大小
        int size = Marshal.SizeOf(typeof(T));
        ByteArray byteArray = new ByteArray(size);

        //分配结构体大小的内存空间
        IntPtr structPtr = Marshal.AllocHGlobal(size);
        //将结构体拷到分配好的内存空间
        Marshal.StructureToPtr(value, structPtr, false);
        //从内存空间拷到byte数组
        Span<byte> simplerSpan = new Span<byte>(structPtr.ToPointer(), size);
        simplerSpan.CopyTo(byteArray.Span);
        //释放内存空间
        Marshal.FreeHGlobal(structPtr);
        //返回对象
        return byteArray;
    }

    /// <summary>
    /// 数据长度类型的长度
    /// </summary>
    /// <param name="valueLengthType">数据长度类型</param>
    /// <returns>长度</returns>
    internal static int Size(this LengthType valueLengthType)
    {
        return (int)valueLengthType;
    }

    /// <summary>
    /// 得到最接近数字且大于数字的二的N次方数
    /// </summary>
    /// <param name="value">数字</param>
    /// <returns>二的N次方数</returns>
    public static int Get2PowNHigh(int value)
    {
        --value;//避免正好输入一个2的次方数
        value |= value >> 1;
        value |= value >> 2;
        value |= value >> 4;
        value |= value >> 8;
        value |= value >> 16;
        return ++value;
    }

    /// <summary>
    /// 得到最接近数字且小于数字的二的N次方数
    /// </summary>
    /// <param name="value">数字</param>
    /// <returns>二的N次方数</returns>
    public static int Get2PowNLow(int value)
    {
        return Get2PowNHigh(value) >> 1;
    }

    internal static int GetLengthValue(this ReadOnlySpan<byte> data, int offset, int length, bool isLittleEndian = true)
    {
        return data.Slice(offset, length).GetLengthValue(isLittleEndian);
    }

    internal static int GetLengthValue(this ReadOnlySpan<byte> span, bool isLittleEndian = true)
    {
        if (span == null)
        {
            return -1;
        }

        if (span.Length == 1)
        {
            return span[0];
        }

        if (span.Length == 2)
        {
            if (isLittleEndian)
                return (short)(span[0] | span[1] << 8);
            else
                return (short)(span[1] | span[0] << 8);
        }

        if (span.Length == 4)
        {
            if (isLittleEndian)
                return (int)(span[0] | span[1] << 8 | span[2] << 16 | span[3] << 24);
            else
                return (int)(span[3] | span[2] << 8 | span[1] << 16 | span[0] << 24);
        }

        return -1;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="buffer"></param>
    /// <param name="startIndex"></param>
    /// <param name="value"></param>
    /// <returns></returns>
    internal static bool TryWriteByte(byte[] buffer, int startIndex, Byte value)
    {
        if (unchecked((uint)startIndex) >= unchecked((uint)buffer.Length))
            ThrowHelper.ThrowArgumentOutOfRangeException();
        if (startIndex > buffer.Length - sizeof(byte))
            ThrowHelper.ThrowArgumentException();

        buffer[startIndex] = value;
        return true;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="buffer"></param>
    /// <param name="startIndex"></param>
    /// <param name="value"></param>
    /// <param name="isLittleEndian"></param>
    /// <returns></returns>
    internal static bool TryWriteUShort(byte[] buffer, int startIndex, ushort value, bool isLittleEndian = true)
    {
        if (unchecked((uint)startIndex) >= unchecked((uint)buffer.Length))
            ThrowHelper.ThrowArgumentOutOfRangeException();
        if (startIndex > buffer.Length - sizeof(short))
            ThrowHelper.ThrowArgumentException();

        Span<byte> bytes = stackalloc byte[sizeof(short)];
        if (isLittleEndian)
        {
            bytes[0] = (byte)value;
            bytes[1] = (byte)(value >> 8);
        }
        else
        {
            bytes[1] = (byte)value;
            bytes[0] = (byte)(value >> 8);
        }

        bytes.CopyTo(buffer.AsSpan().Slice(startIndex, sizeof(ushort)));
        return true;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="buffer"></param>
    /// <param name="startIndex"></param>
    /// <param name="value"></param>
    /// <param name="isLittleEndian">字节顺序，true为小端字节(低字节序)，false为大端字节序(高字节序)</param>
    /// <returns></returns>
    internal static bool TryWriteUInt(byte[] buffer, int startIndex, uint value, bool isLittleEndian = true)
    {
        if (unchecked((uint)startIndex) >= unchecked((uint)buffer.Length))
            ThrowHelper.ThrowArgumentOutOfRangeException();
        if (startIndex > buffer.Length - sizeof(int))
            ThrowHelper.ThrowArgumentException();

        Span<byte> bytes = stackalloc byte[sizeof(int)];
        if (isLittleEndian)
        {
            bytes[0] = (byte)value;
            bytes[1] = (byte)(value >> 8);
            bytes[2] = (byte)(value >> 16);
            bytes[3] = (byte)(value >> 24);
        }
        else
        {
            bytes[3] = (byte)value;
            bytes[2] = (byte)(value >> 8);
            bytes[1] = (byte)(value >> 16);
            bytes[0] = (byte)(value >> 24);
        }

        bytes.CopyTo(buffer.AsSpan().Slice(startIndex, sizeof(uint)));
        return true;
    }

    /// <summary>
    /// 返回字节帧对象
    /// </summary>
    /// <param name="e">字节帧结构</param>
    /// <returns>字节帧对象</returns>
    public static ByteFrame ByteFrame(this ByteFrameEventArgs e)
    {
        return new ByteFrame(e);
    }
}

internal static class EmptyArray<T>
{
    public static readonly T[] Value = new T[0];
}

internal static class StringBuilderCache
{
    // The value 360 was chosen in discussion with performance experts as a compromise between using
    // as litle memory (per thread) as possible and still covering a large part of short-lived
    // StringBuilder creations on the startup path of VS designers.
    private const int MAX_BUILDER_SIZE = 360;

    [ThreadStatic]
    private static StringBuilder CachedInstance;

    internal const int DefaultCapacity = 16;

    public static StringBuilder Acquire(int capacity = DefaultCapacity)
    {
        if (capacity <= MAX_BUILDER_SIZE)
        {
            StringBuilder sb = StringBuilderCache.CachedInstance;
            if (sb != null)
            {
                // Avoid stringbuilder block fragmentation by getting a new StringBuilder
                // when the requested size is larger than the current capacity
                if (capacity <= sb.Capacity)
                {
                    StringBuilderCache.CachedInstance = null;
                    sb.Clear();
                    return sb;
                }
            }
        }
        return new StringBuilder(capacity);
    }

    public static void Release(StringBuilder sb)
    {
        if (sb.Capacity <= MAX_BUILDER_SIZE)
        {
            StringBuilderCache.CachedInstance = sb;
        }
    }

    public static string GetStringAndRelease(StringBuilder sb)
    {
        string result = sb.ToString();
        Release(sb);
        return result;
    }
}
